/* * Copyright 2012 James Moger * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.moxie.proxy; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.text.MessageFormat; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import org.restlet.Component; import org.restlet.Server; import org.restlet.data.Protocol; import org.restlet.engine.Engine; import org.restlet.routing.VirtualHost; import org.restlet.util.Series; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; /** * Launcher class that starts a Restlet server. * * @author James Moger * */ public class Launcher { public static void main(String[] args) throws Exception { Params params = new Params(); JCommander jc = new JCommander(params); try { jc.parse(args); } catch (Exception e) { e.printStackTrace(); jc.usage(); System.exit(-1); } ProxyConfig config = new ProxyConfig(); // parse config file, allow override if (params.moxieConfig.exists()) { config.parse(params.moxieConfig); } else { // default to user directory config.setUserDefaults(); } // override defaults from command-line if (params.httpPort != null) { config.setHttpPort(params.httpPort); } if (params.httpsPort != null) { config.setHttpsPort(params.httpsPort); } if (params.proxyPort != null) { config.setProxyPort(params.proxyPort); } if (params.shutdownPort != null) { config.setShutdownPort(params.shutdownPort); } if (params.accesslog != null) { config.setAccessLog(params.accesslog); } if (params.moxieRoot != null) { config.setMoxieRoot(params.moxieRoot); } if (params.storePassword != null) { config.setStorePassword(params.storePassword); } // start or stop server if (params.stop) { stop(config); } else { start(config); } } /** * Stop Moxie Proxy. */ static void stop(ProxyConfig config) { try { Socket s = new Socket(InetAddress.getByName("127.0.0.1"), config.getShutdownPort()); OutputStream out = s.getOutputStream(); System.out.println("Sending shutdown request to " + Constants.getName()); out.write("\r\n".getBytes()); out.flush(); s.close(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * Start Moxie Proxy * @param config */ static void start(ProxyConfig config) throws Exception { Engine.setRestletLogLevel(Level.INFO); Component c = new Component(); // turn off Restlet url access logging c.getLogService().setEnabled(config.getAccessLog()); // specify the Jetty helpers String httpHelper = "org.restlet.ext.jetty.HttpServerHelper"; String httpsHelper = "org.restlet.ext.jetty.HttpsServerHelper"; // create a Restlet server if (config.getBindAddresses().size() == 0) { // start server on all available addresses if (config.getHttpPort() > 0) { Server server = new Server(null, Arrays.asList(Protocol.HTTP), null, config.getHttpPort(), c, httpHelper); c.getServers().add(server); } if (config.getHttpsPort() > 0) { Server server = new Server(null, Arrays.asList(Protocol.HTTPS), null, config.getHttpsPort(), c, httpsHelper); c.getServers().add(server); configureHttps(server, config); } } else { // start server on specific address(es) for (String address : config.getBindAddresses()) { if (config.getHttpPort() > 0) { Server server = new Server(null, Arrays.asList(Protocol.HTTP), address, config.getHttpPort(), c, httpHelper); c.getServers().add(server); } if (config.getHttpsPort() > 0) { Server server = new Server(null, Arrays.asList(Protocol.HTTPS), address, config.getHttpsPort(), c, httpsHelper); c.getServers().add(server); configureHttps(server, config); } } } // add client classpath protocol to enable resource loading from the jar c.getClients().add(Protocol.CLAP); // add client file protocol to enable serving artifacts from filesystem c.getClients().add(Protocol.FILE); // override the default error pages c.setStatusService(new ErrorStatusService(c.getContext())); // get the default virtual host VirtualHost host = c.getDefaultHost(); MoxieProxy app = new MoxieProxy(config); // Guard Moxie Proxy with BASIC authentication. Authenticator guard = new Authenticator(app); host.attachDefault(guard); guard.setNext(app); // start the shutdown monitor if (config.getShutdownPort() > 0) { Thread shutdownMonitor = new ShutdownMonitorThread(c, app, config); shutdownMonitor.start(); } // start the Restlet http/https server c.start(); } /** * Configure the SSL keystore for an https server. * * @param server */ static void configureHttps(Server server, ProxyConfig config) { File keystore = new File("keystore"); if (!keystore.exists()) { server.getLogger().info("Generating self-signed SSL certificate for localhost"); MakeCertificate.generateSelfSignedCertificate("localhost", keystore, config.getKeystorePassword()); } Series<org.restlet.data.Parameter> parameters = server.getContext().getParameters(); parameters.add("keystorePath", keystore.getAbsolutePath()); parameters.add("keystorePassword", config.getKeystorePassword()); } /** * JCommander Parameters class. */ @Parameters(separators = " ") private static class Params { @Parameter(names = { "--httpPort" }, description = "port for serving web interface", required = false) public Integer httpPort; @Parameter(names = { "--httpsPort" }, description = "secure port for serving web interface", required = false) public Integer httpsPort; @Parameter(names = { "--proxyPort" }, description = "port for processing proxy requests", required = false) public Integer proxyPort; @Parameter(names = { "--shutdownPort" }, description = "port for shutdown monitor to listen on", required = false) public Integer shutdownPort; @Parameter(names = { "--accesslog" }, description = "log all url requests", required = false) public Boolean accesslog; @Parameter(names = { "--root" }, description = "folder for Moxie metadata and artifacts", required = false) public File moxieRoot; @Parameter(names = { "--config" }, description = "config file for Moxie Proxy", required = false) public File moxieConfig = new File("proxy.moxie"); @Parameter(names = { "--storePassword" }, description = "password for JKS keystore", required = false) public String storePassword; @Parameter(names = { "--stop" }, description = "stops the Moxie Proxy server", required = false) public boolean stop; } /** * The ShutdownMonitorThread opens a socket on a specified port and waits * for an incoming connection. When that connection is accepted a shutdown * message is issued to the running Moxie Proxy server. * * @author James Moger * */ private static class ShutdownMonitorThread extends Thread { private final ServerSocket socket; private final Component c; private final MoxieProxy app; private final Logger logger = Logger.getLogger(ShutdownMonitorThread.class.getSimpleName()); public ShutdownMonitorThread(Component c, MoxieProxy app, ProxyConfig config) { this.c = c; this.app = app; setDaemon(true); setName("internal [SHUTDOWN] server"); ServerSocket skt = null; try { skt = new ServerSocket(config.getShutdownPort(), 1, InetAddress.getByName("127.0.0.1")); } catch (Exception e) { logger.log(Level.WARNING, "Could not open shutdown monitor on port " + config.getShutdownPort(), e); } socket = skt; } @Override public void run() { logger.info(MessageFormat.format(Constants.MESSAGE_STARTUP, "SHUTDOWN", socket.getLocalPort())); Socket accept; try { accept = socket.accept(); BufferedReader reader = new BufferedReader(new InputStreamReader(accept.getInputStream())); reader.readLine(); c.stop(); logger.info(MessageFormat.format(Constants.MESSAGE_SHUTDOWN, "PROXY")); accept.close(); socket.close(); logger.info(MessageFormat.format(Constants.MESSAGE_SHUTDOWN, "SHUTDOWN")); } catch (Exception e) { logger.log(Level.WARNING, "Failed to shutdown " + Constants.getName(), e); } } } }